///////////////////////////////////////////////////////////////////////////////
////  (c) Chaichuk AU (chaichuk@mail.ru), 								   ////
////  2005, Chelyabinsk, NTC Privodnaya Tehnika (momentum@chel.surnet.ru)  ////
///////////////////////////////////////////////////////////////////////////////

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "anybus.h"
#include "anybus_dpm.h"

//#define DEBUG_ANYBUS_C

//----------------------------------------------------------------------------
void abs_update_status (abs_module_t *abs)
{
	uint8_t indic_register_rw;
	uint8_t indic_register_ro;	

	#ifdef DEBUG_ANYBUS_C 
	printf (" --> abs_update_status\n"); fflush (stdout);  delay (1); 
	#endif 

//	delay (1);
	indic_register_rw = *abs->indic_register_rw_ptr;
	indic_register_ro = *abs->indic_register_ro_ptr;	
	
	if (indic_register_ro & ABS_INIT) abs->abs_initialised = 1;
	else abs->abs_initialised = 0;
	
	if (indic_register_ro & ABS_AB_IN) abs->in_area_free = 1;
	else abs->in_area_free = 0;

	if (indic_register_ro & ABS_AB_OUT) abs->out_area_free = 1;
	else abs->out_area_free = 0;

	if (indic_register_ro & ABS_AB_FBCTRL) abs->fbctrl_area_free = 1;
	else abs->fbctrl_area_free = 0;

	if ((indic_register_ro & ABS_AB_MOUT) != (indic_register_rw & ABS_AP_MOUT)) abs->mbx_out_new = 1;
	else abs->mbx_out_new = 0;

	if ((indic_register_ro & ABS_AB_MIN) == (indic_register_rw & ABS_AP_MIN)) abs->mbx_in_free = 1;
	else abs->mbx_in_free = 0;

	if ((indic_register_ro & ABS_AB_EVNT) != (indic_register_rw & ABS_AP_EVNT)) abs->new_event = 1;
	else abs->new_event = 0;	
		
	#ifdef DEBUG_ANYBUS_C 
	printf (" <-- abs_update_status return\n"); fflush (stdout);  delay (1); 
	#endif
}

//----------------------------------------------------------------------------
void abs_write_ind_bits (abs_module_t* abs, uint8_t c_bits, uint8_t s_bits, uint8_t t_bits)
{
	uint8_t app_indreg_copy;

	app_indreg_copy  =  *abs->indic_register_rw_ptr;
	app_indreg_copy &= ~c_bits;
	app_indreg_copy |=  s_bits;
	app_indreg_copy ^=  t_bits;

	do {
		*abs->indic_register_rw_ptr = app_indreg_copy;
	} while (*abs->indic_register_rw_ptr != app_indreg_copy);
}

//----------------------------------------------------------------------------
int abs_send_mbx_msg (abs_module_t *abs, uint16_t *msg_id, const uint8_t *msg_hdr, 
						const uint8_t *msg_ext_hdr, const uint8_t *msg_data)
{
	static uint16_t id;
	uint8_t *ptr = abs->mailbox_input_area_ptr;
	
	if (!abs->mbx_in_free) {
		//fprintf (stderr, "mailbox input area is currently in use\n");
		return (-1);
	}
	
	*msg_id = id ++;
	memcpy (ptr, msg_id, 2);
	ptr += 2;
	memcpy (ptr, msg_hdr, 14);
	ptr += 14;
	if (msg_ext_hdr) memcpy (ptr, msg_ext_hdr, 16);
	ptr += 16;
	if (msg_data) memcpy (ptr, msg_data, (msg_hdr [4] << 8) + msg_hdr [5]);
	
	abs_write_ind_bits (abs, 0, 0, ABS_AP_MIN);
		
	return 0;
}

//----------------------------------------------------------------------------
int abs_recv_mbx_msg (abs_module_t *abs, const uint16_t msg_id, uint8_t *msg_hdr, 
						uint8_t *msg_ext_hdr, uint8_t *msg_data, int data_size)
{
	uint16_t id;
	uint8_t *ptr = abs->mailbox_output_area_ptr;
	uint16_t recv_data_size;
	
	if (!abs->mbx_out_new) {
		//fprintf (stderr, "no messages available in the mailbox output area\n");		
		return (-1);
	}
	
	memcpy (&id, ptr, 2);
	if (id != msg_id) {
		fprintf (stderr, "wrong msg_id %d (expected %d)\n", id, msg_id);
		return -2;
	}
	ptr += 2;	
	memcpy (msg_hdr, ptr, 14);
	ptr += 14;
	if (msg_ext_hdr) memcpy (msg_ext_hdr, ptr, 16);
	ptr += 16;
	if (msg_data) { 
		recv_data_size = (msg_hdr [4] << 8) + msg_hdr [5];
		//printf ("received %d data bytes\n", recv_data_size);
		if (recv_data_size > data_size) {
			fprintf (stderr, "too big msg_data %d bytes (expected max %d bytes)\n", 
					recv_data_size, data_size);
			return -2;
		}
		memcpy (msg_data, ptr, recv_data_size);
	}

	abs_write_ind_bits (abs, 0, 0, ABS_AP_MOUT);
		
	return 0;
}

//----------------------------------------------------------------------------
void abs_request_release_area (abs_module_t *abs, uint8_t request, uint8_t locked, uint8_t areas)
{
	uint8_t indic_register_rw = *abs->indic_register_rw_ptr;	

	indic_register_rw &= ~(ABS_ACTION | ABS_LOCK | ABS_AP_IN | ABS_AP_OUT | ABS_AP_FBCTRL);

	if (request) {
		indic_register_rw |= ABS_ACTION;
	}

	if (locked) {
		indic_register_rw |= ABS_LOCK;
	}

	indic_register_rw |= areas;

	*abs->indic_register_rw_ptr = indic_register_rw;
}

//----------------------------------------------------------------------------
void abs_request_input_data_area (abs_module_t *abs)
{
	abs_request_release_area (abs, 1, 1, ABS_AP_IN);
}

//----------------------------------------------------------------------------
void abs_release_input_data_area (abs_module_t *abs)
{
	abs_request_release_area (abs, 0, 0, ABS_AP_IN);
}

//----------------------------------------------------------------------------
void abs_request_output_data_area (abs_module_t *abs)
{
	abs_request_release_area (abs, 1, 1, ABS_AP_OUT);
}

//----------------------------------------------------------------------------
void abs_release_output_data_area (abs_module_t *abs)
{
	abs_request_release_area (abs, 0, 0, ABS_AP_OUT);
}

//----------------------------------------------------------------------------
void abs_request_fbctrl_area (abs_module_t *abs)
{
	abs_request_release_area (abs, 1, 1, ABS_AP_FBCTRL);
}

//----------------------------------------------------------------------------
void abs_release_fbctrl_area (abs_module_t *abs)
{
	abs_request_release_area (abs, 0, 0, ABS_AP_FBCTRL);
}

//----------------------------------------------------------------------------
int abs_init (abs_module_t *abs)
{
	uint16_t msg_id;
	uint16_t fault_info;
	
	const uint8_t start_init []      	= { 0x40, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
	const uint8_t anybus_init []     	= { 0x40, 0x01, 0x00, 0x02, 0x00, 0x12, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
	const uint8_t end_init []        	= { 0x40, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
	uint8_t anybus_init_data [18];
	
	uint8_t start_init_resp [16];
	uint8_t anybus_init_resp [14];
	uint8_t anybus_init_resp_ext [16];
	uint8_t anybus_init_data_resp [18];
	uint8_t end_init_resp [14];
	

	#ifdef DEBUG_ANYBUS_C
	printf (".. abs_dpm_get_db_info\n"); fflush (stdout);  delay (2);		
	#endif
	abs_dpm_get_db_info (abs, &abs->total_input_len, &abs->total_output_len, 
								&abs->init_input_len, &abs->init_output_len);
								
	abs->input_buf = malloc (abs->init_input_len);
	abs->output_buf = malloc (abs->init_output_len);

	#ifdef DEBUG_ANYBUS_C
	printf (".. start_init\n"); fflush (stdout);  delay (2);		
	#endif
	do {
		abs_update_status (abs);	
	} while (abs_send_mbx_msg (abs, &msg_id, start_init, NULL, NULL) == -1);
	do {
		abs_update_status (abs);
	} while (abs_recv_mbx_msg (abs, msg_id, start_init_resp, NULL, NULL, 0) == -1);
	if (start_init_resp [0] & 0x80) {
		fprintf (stderr, "start_init_response error (code = 0x%X)!\n", start_init_resp [0]);
		return -1;
	}

	#ifdef DEBUG_ANYBUS_C
	printf (".. abs_dpm_init ..\n"); fflush (stdout);  delay (2);		
	#endif
	abs_dpm_init (abs, ABS_DPM_DPRAM_EXTENDED, ABS_DPM_STARTUP_OPERATION);

	// Input IO Length
	anybus_init_data [0]  = (uint8_t) (abs->init_output_len >> 8);
	anybus_init_data [1]  = (uint8_t) (abs->init_output_len >> 0);	
	// Input DPRAM Length
	anybus_init_data [2]  = anybus_init_data [0];
	anybus_init_data [3]  = anybus_init_data [1];
	// Input Total Length
	anybus_init_data [4]  = anybus_init_data [0];
	anybus_init_data [5]  = anybus_init_data [1];
	// Output IO Length
	anybus_init_data [6]  = (uint8_t) (abs->init_input_len >> 8);
	anybus_init_data [7]  = (uint8_t) (abs->init_input_len >> 0);	
	// Output DPRAM Length
	anybus_init_data [8]  = anybus_init_data [6];
	anybus_init_data [9]  = anybus_init_data [7];
	// Output Total Length
	anybus_init_data [10] = anybus_init_data [6];
	anybus_init_data [11] = anybus_init_data [7];
	// Operation Mode
	anybus_init_data [12] = 0x00;
	anybus_init_data [13] = 0x10;	
	// Event Notification Config
	anybus_init_data [14] = 0x00;
	anybus_init_data [15] = 0x08;
	// Watchdog Timeout Event
	anybus_init_data [16] = 0x00;
	anybus_init_data [17] = 0x00;

	//abs_print_msg (anybus_init_data, 18);
	
	#ifdef DEBUG_ANYBUS_C 
	printf (".. anybus_init\n"); fflush (stdout);  delay (2); 
	#endif
	do {
		abs_update_status (abs);
	} while (abs_send_mbx_msg (abs, &msg_id, anybus_init, NULL, anybus_init_data) == -1);
	do {
		abs_update_status (abs);
	} while (abs_recv_mbx_msg (abs, msg_id, anybus_init_resp, anybus_init_resp_ext, 
			anybus_init_data_resp, sizeof (anybus_init_data_resp)) == -1);
	if (anybus_init_resp [0] & 0x80) {
		fprintf (stderr, "anybus_init_response error (code = 0x%X)!\n", anybus_init_resp [0]);
		if ((anybus_init_resp [0] & 0x0F) == 0x0F) {
			//abs_print_msg (anybus_init_resp_ext, 16);
			fault_info = (anybus_init_resp_ext [14] << 8) + anybus_init_resp_ext [15];
			fprintf (stderr, "anybus_init error:\n");
			if (fault_info & (1 << 0)) fprintf (stderr, "'Input IO Length' value is incorrect\n");
			if (fault_info & (1 << 1)) fprintf (stderr, "'Input DPRAM Length' value is incorrect\n");
			if (fault_info & (1 << 2)) fprintf (stderr, "'Input Total Length' value is incorrect\n");
			if (fault_info & (1 << 3)) fprintf (stderr, "'Output IO Length' value is incorrect\n");
			if (fault_info & (1 << 4)) fprintf (stderr, "'Output DPRAM Length' value is incorrect\n");
			if (fault_info & (1 << 5)) fprintf (stderr, "'Output Total Length' value is incorrect\n");
			if (fault_info & (1 << 7)) fprintf (stderr, "'Module Status' value is incorrect\n");
			if (fault_info & (1 << 8)) fprintf (stderr, "'Event Notification' value is incorrect\n");
			if (fault_info & (1 << 9)) fprintf (stderr, "Incorrect Watchdog Counter difference value\n");
		}
		return -1;
	}

	#ifdef DEBUG_ANYBUS_C 
	printf (".. end_init\n"); fflush (stdout);  delay (2); 
	#endif
	do {
		abs_update_status (abs);
	} while (abs_send_mbx_msg (abs, &msg_id, end_init, NULL, NULL) == -1);	
	do {
		abs_update_status (abs);
	} while (abs_recv_mbx_msg (abs, msg_id, end_init_resp, NULL, NULL, 0) == -1);
	if (end_init_resp [0] & 0x80) {
		fprintf (stderr, "end_init_response error (code = 0x%X)!\n", end_init_resp [0]);
		return -1;
	}

	#ifdef DEBUG_ANYBUS_C 
	printf (".. return 0\n"); fflush (stdout);  delay (200); 
	#endif
	return 0;
}

//----------------------------------------------------------------------------
int abs_sw_reset (abs_module_t *abs)
{
	uint16_t msg_id;
	const uint8_t sw_reset_msg []   	= { 0x40, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
	uint8_t sw_reset_resp [14];
	
	do {
		abs_update_status (abs);	
	} while (abs_send_mbx_msg (abs, &msg_id, sw_reset_msg, NULL, NULL) == -1);
	do {
		abs_update_status (abs);
	} while (abs_recv_mbx_msg (abs, msg_id, sw_reset_resp, NULL, NULL, 0) == -1);
	if (sw_reset_resp [0] & 0x80) {
		fprintf (stderr, "sw_reset_resp error (code = 0x%X)!\n", sw_reset_resp [0]);
		return -1;
	}
	
	return 0;	
}

//----------------------------------------------------------------------------
void abs_print_msg (uint8_t *msg, int size)
{
	int i;
	
	for (i = 0; i < size; i += 2) {
		printf ("%3X: %02X-%02X\n", i, msg [i], msg [i + 1]);
	}	
}
